﻿//===============================================================================
// Jammer.NET Development Library
// Jmr.Entity.Poco
//===============================================================================
// Copyright © Junaid Fakhruddin, Matthew Leedham and Randolph Cabral.  
// All rights reserved.
//===============================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlTypes;
using System.Linq;
using System.Linq.Expressions;
using System.Transactions;
using Jmr.DependencyInjection;
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data.SqlClient;
using IQToolkit.Data;
using System.Configuration;
using IQToolkit;
using System.Text;

namespace Jmr.Entity.Poco
{
	public class DataSession<TDatabaseMapper> : IDisposable
		where TDatabaseMapper : DatabaseMapper
	{
		public DataSession()
		{
			DbMap = Activator.CreateInstance<TDatabaseMapper>();
			ParmCache = DependencyResolver<DataSessionDependencyMappings>.GetInstance<ParameterCache>();
			TransactionTimeout = new TimeSpan(0, 0, 60);
			CommandTimeout = 30;
		}

		public TimeSpan TransactionTimeout { get; set; }

		/// <summary>
		/// Specifies the timeout period required for sql execution. The default timeout is 30 seconds
		/// </summary>
		public int CommandTimeout { get; set; }

		private TDatabaseMapper _dbmap = default(TDatabaseMapper);
		public TDatabaseMapper DbMap
		{
			get { return _dbmap; }
			private set { _dbmap = value; }
		}

		private ParameterCache _parmCache;
		public ParameterCache ParmCache
		{
			get { return _parmCache; }
			set { _parmCache = value; }
		}

		#region Not Needed

		//public TEntity FetchSingle<TEntity>(object entityarg)
		//{
		//  return (TEntity)FetchSingle(typeof(TEntity), entityarg);
		//}

		//public object FetchSingle(Type type, object entityarg)
		//{
		//  var map = DbMap.GetMap(type);
		//  var entityargname = (entityarg == null) ? type.FullName : entityarg.GetType().FullName;
		//  var sprocname = map.SprocMaps.Single(e => 
		//    e.CrudType == CrudType.Select 
		//    && e.EntityArgType.FullName == entityargname).SprocName;
		//  var set = ExecuteQuerySP(type, sprocname, entityarg);
		//  var setcount = set.Cast<object>().Count();

		//  if (setcount != 1)
		//    throw new NotSupportedException(string.Format("{0} results in the set. Expected exactly one result.", setcount));

		//  var enumerator = set.GetEnumerator();
		//  enumerator.MoveNext();
		//  var ret = enumerator.Current;
		//  return ret;
		//}

		//public IEnumerable<TEntity> FetchSet<TEntity>(object entityarg)
		//{
		//  return FetchSet(typeof(TEntity), entityarg).Cast<TEntity>();
		//}

		//public IEnumerable FetchSet(Type type, object entityarg)
		//{
		//  var map = DbMap.GetMap(type);
		//  var entityargname = (entityarg == null) ? type.FullName : entityarg.GetType().FullName;
		//  var sprocname = map.SprocMaps.Single(e =>
		//    e.CrudType == CrudType.SelectMany
		//    && e.EntityArgType.FullName == entityargname).SprocName;
		//  var ret = ExecuteQuerySP(type, sprocname, entityarg);
		//  return ret;
		//}

		#endregion

		public void LoadEntityProxy<TEntity>(TEntity entity, object entityarg)
		{
			LoadEntity(entity, entityarg, true);
		}

		public void LoadEntity<TEntity>(TEntity entity, object entityarg, bool isProxyType)
		{
			Type basetype = entity.GetType();
			if (isProxyType) basetype = entity.GetType().BaseType;

			var map = DbMap.GetMap(basetype);
			var argmap = DbMap.GetMap(entityarg.GetType());
			var db = DatabaseFactory.CreateDatabase(map.DatabaseName);

			var entityargname = (entityarg == null) ? basetype.FullName : argmap.EntityType.FullName;
			var sprocname = map.SprocMaps.Single(e =>
				e.CrudType == CrudType.Select
				&& e.EntityArgType.FullName == entityargname).SprocName;

			if (string.IsNullOrEmpty(sprocname)) { return; }

			var cmd = db.GetStoredProcCommand(sprocname);
			cmd.CommandTimeout = CommandTimeout;
			ParmCache.SetParameters(cmd, db);
			MapSprocParameterValues(cmd, entityarg);
			IDataReader reader = null;
			try
			{
				reader = db.ExecuteReader(cmd);
				while (reader.Read())
				{
					if (isProxyType)
						FillLazyEntityPropertyValues(reader, entity);
					else
						FillEntityPropertyValues(reader, entity);
				}
			}
			finally
			{
				reader.Close();
			}
		}

		public void ReloadEntity<TEntity>(TEntity entityarg)
		{
			var map = DbMap.GetMap(entityarg.GetType());
			var db = DatabaseFactory.CreateDatabase(map.DatabaseName);

			var sprocmap = map.SprocMaps.SingleOrDefault(e =>
				e.CrudType == CrudType.Select
				&& e.EntityArgType.FullName == map.EntityType.FullName);

			if (sprocmap == null)
				sprocmap = map.SprocMaps.SingleOrDefault(e =>
					e.CrudType == CrudType.Select
					&& e.EntityArgType.FullName == entityarg.GetType().BaseType.FullName);

			var sprocname = sprocmap.SprocName;

			if (string.IsNullOrEmpty(sprocname)) { return; }

			var cmd = db.GetStoredProcCommand(sprocname);
			cmd.CommandTimeout = CommandTimeout;
			ParmCache.SetParameters(cmd, db);
			MapSprocParameterValues(cmd, entityarg);
			IDataReader reader = null;
			try
			{
				reader = db.ExecuteReader(cmd);
				while (reader.Read())
					FillEntityPropertyValues(reader, entityarg);
			}
			finally
			{
				reader.Close();
			}
		}

		public int Insert<TEntity>(TEntity entity)
		{
			return Insert(entity, true);
		}

		public int Insert<TEntity>(TEntity entity, bool reload)
		{
			//var map = DbMap.GetMap(entity.GetType());
			var ret = ExecuteDML(entity, entity, CrudType.Insert);
			if (reload) { ReloadEntity(entity); }
			return ret;
		}

		public int Update<TEntity>(TEntity entity)
		{
			return Update(entity, true);
		}

		public int Update<TEntity>(TEntity entity, bool reload)
		{
			//var map = DbMap.GetMap(entity.GetType());
			var ret = ExecuteDML(entity, entity, CrudType.Update);
			if (reload) { ReloadEntity(entity); }
			return ret;
		}

		public int Delete<TEntity>(TEntity entity)
		{
			var map = DbMap.GetMap(entity.GetType());
			return ExecuteDML(entity, entity, CrudType.Delete);
		}

		public int InsertBatch(Type entitytype, IEnumerable entities)
		{
			var retval = 0;
			//var map = DbMap.GetMap(entitytype);
			using (var tx = new TransactionScope(TransactionScopeOption.Required, TransactionTimeout))
			{
				foreach (var entity in entities)
					retval += Insert(entity, false);

				tx.Complete();
			}

			return retval;
		}

		public int InsertBatch<TEntity>(IEnumerable<TEntity> entities)
		{
			return InsertBatch(typeof(TEntity), entities);
		}

		public int UpdateBatch(Type entitytype, IEnumerable entities)
		{
			var retval = 0;
			//var map = DbMap.GetMap(entitytype);
			using (var tx = new TransactionScope(TransactionScopeOption.Required, TransactionTimeout))
			{
				foreach (var entity in entities)
					retval += Update(entity, false);

				tx.Complete();
			}
			return retval;
		}

		public int UpdateBatch<TEntity>(IEnumerable<TEntity> entities)
		{
			return UpdateBatch(typeof(TEntity), entities);
		}

		public int DeleteBatch(Type entitytype, IEnumerable entities)
		{
			var retval = 0;
			//var map = DbMap.GetMap(entitytype);
			using (var tx = new TransactionScope(TransactionScopeOption.Required, TransactionTimeout))
			{
				foreach (var entity in entities)
					retval += Delete(entity);

				tx.Complete();
			}
			return retval;
		}

		public int DeleteBatch<TEntity>(IEnumerable<TEntity> entities)
		{
			return DeleteBatch(typeof(TEntity), entities);
		}

		public int ExecuteNonQuery(string commandString, string databaseName)
		{
			var db = DatabaseFactory.CreateDatabase(databaseName);
			var cmd = db.GetSqlStringCommand(commandString);
			cmd.CommandTimeout = CommandTimeout;
			return db.ExecuteNonQuery(cmd);
		}

		public Database GetDatabase(string databaseName)
		{
			return DatabaseFactory.CreateDatabase(databaseName);
		}

		public IQueryable<TEntity> AsQueryable<TEntity>()
		{
			var map = DbMap.GetMap(typeof(TEntity));
			string connString = ConfigurationManager.ConnectionStrings[map.DatabaseName].ConnectionString;
			var provider = new JmrEntityProvider(typeof(TEntity), connString, DbMap);
			return provider.GetTable<TEntity>(map.TableName).AsQueryable();
		}

		public IQueryable AsQueryable(Type entityType)
		{
			var map = DbMap.GetMap(entityType);
			string connString = ConfigurationManager.ConnectionStrings[map.DatabaseName].ConnectionString;
			var provider = new JmrEntityProvider(entityType, connString, DbMap);
			return provider.GetTable(entityType, map.TableName).AsQueryable();
		}


		public IEnumerable<TEntity> ExecuteQuery<TEntity>(string commandString)
		{
			var result = ExecuteQuery(typeof(TEntity), commandString);
			return result.Cast<TEntity>();
		}

		public IEnumerable ExecuteQuery(Type type, string commandString)
		{
			var map = DbMap.GetMap(type);
			var ret = new ArrayList();
			var db = DatabaseFactory.CreateDatabase(map.DatabaseName);
			var cmd = db.GetSqlStringCommand(commandString);
			cmd.CommandTimeout = CommandTimeout;
			FillEntityList(type, ret, db, cmd);
			return ret;
		}

		public IEnumerable<TEntity> ExecuteQuerySP<TEntity>(string sprocName, object entityarg)
		{
			var result = ExecuteQuerySP(typeof(TEntity), sprocName, entityarg);
			return result.Cast<TEntity>();
		}

		public IEnumerable ExecuteQuerySP(Type type, string sprocName, object entityarg)
		{
			var entity = Activator.CreateInstance(type);
			var map = DbMap.GetMap(type);
			var ret = new ArrayList();
			var db = DatabaseFactory.CreateDatabase(map.DatabaseName);
			var cmd = db.GetStoredProcCommand(sprocName);
			cmd.CommandTimeout = CommandTimeout;
			ParmCache.SetParameters(cmd, db);
			MapSprocParameterValues(cmd, entityarg);
			FillEntityList(type, ret, db, cmd);
			return ret;
		}

		public int ExecuteNonQuerySP<TEntity>(string sprocName, object entityarg)
		{
			return ExecuteNonQuerySP(typeof(TEntity), sprocName, entityarg);
		}

		public int ExecuteNonQuerySP(Type type, string sprocName, object entityarg)
		{
			var map = DbMap.GetMap(type);
			var db = DatabaseFactory.CreateDatabase(map.DatabaseName);
			var cmd = db.GetStoredProcCommand(sprocName);
			cmd.CommandTimeout = CommandTimeout;
			ParmCache.SetParameters(cmd, db);
			MapSprocParameterValues(cmd, entityarg);
			var ret = db.ExecuteNonQuery(cmd);
			return ret;
		}

		private int ExecuteDML<TEntity, TEntityArg>(TEntity entity, TEntityArg entityarg, CrudType crudType)
		{
			var map = DbMap.GetMap(entity.GetType());
			var db = DatabaseFactory.CreateDatabase(map.DatabaseName);
			var sprocName = ResolveSprocName(entity.GetType(), entityarg.GetType(), crudType);
			var sqlString = (string.IsNullOrEmpty(sprocName)) ? ResolveSqlString(entity.GetType(), entityarg.GetType(), crudType) : string.Empty;
			DbCommand cmd = null;
			cmd = (string.IsNullOrEmpty(sprocName)) 
				? db.GetSqlStringCommand(sqlString) : db.GetStoredProcCommand(sprocName);
			cmd.CommandTimeout = CommandTimeout;

			if (cmd.CommandType == CommandType.StoredProcedure)
				ParmCache.SetParameters(cmd, db);
			if (cmd.CommandType == CommandType.Text)
				SetParameters(cmd, entityarg);
			
			MapSprocParameterValues(cmd, entityarg);

			var ret = db.ExecuteNonQuery(cmd);
			AssignOutputParams(map, entity, cmd);

			return ret;
		}

		private void SetParameters<TEntity>(DbCommand cmd, TEntity entity)
		{
			var map = DbMap.GetMap(entity.GetType());
			foreach (var colmap in map.ColumnMaps)
			{
				var colname = "@"+colmap.ColumnName;
				var sqlparm = new SqlParameter(colname, DBNull.Value);
				if (colmap.IsPrimaryKey && colmap.IsPrimaryKeySeededByDb)
					sqlparm.Direction = ParameterDirection.InputOutput;
				cmd.Parameters.Add(sqlparm);
			}
		}

		private void AssignOutputParams<TEntity>(DatabaseMap map, TEntity entityarg, DbCommand cmd)
		{
			for (int i = 0; i < cmd.Parameters.Count; i++) // SqlParameter param in _command.Parameters)
			{
				DbParameter param = cmd.Parameters[i];

				if (param.Direction == ParameterDirection.InputOutput || param.Direction == ParameterDirection.Output)
				{
					var paramName = param.ParameterName.Replace("@", string.Empty);

					var entity = map.ColumnMaps.SingleOrDefault(c => c.ColumnName == paramName);
					if (entity != null)
					{
						var propertyName = entity.PropertyName;
						entityarg.GetType().GetProperty(propertyName).SetValue(entityarg, param.Value, null);
					}
				}
			}
		}

		private void FillLazyEntityPropertyValues<TEntity>(IDataReader reader, TEntity entity)
		{
			var map = DbMap.GetMap(typeof(TEntity).BaseType);
			foreach (var item in map.ColumnMaps)
			{
				var sourceValue = reader[item.ColumnName];

				if (sourceValue == null)
					throw new FieldAccessException(String.Format("The column '{0}', does not exist in the DataRow.", item.ColumnName));

				if (!(sourceValue is System.DBNull))
				{
					entity.GetType().GetProperty(item.PropertyName).SetValue(entity, sourceValue, null);
				}
			}
		}

		public void FillEntityPropertyValues<TEntity>(IDataReader reader, TEntity entity)
		{
			var map = DbMap.GetMap(entity.GetType());
			foreach (var item in map.ColumnMaps)
			{
				var sourceValue = reader[item.ColumnName];

				if (sourceValue == null)
					throw new FieldAccessException(String.Format("The column '{0}', does not exist in the DataRow.", item.ColumnName));

				if (!(sourceValue is System.DBNull))
				{
					entity.GetType().GetProperty(item.PropertyName).SetValue(entity, sourceValue, null);
				}
			}
		}

		public void FillEntityList<TEntity>(List<TEntity> list, Database database, DbCommand command)
		{
			var map = DbMap.GetMap(typeof(TEntity));
			IDataReader reader = null;
			try
			{
				reader = database.ExecuteReader(command);
				while (reader.Read())
				{
					var entity = Activator.CreateInstance<TEntity>();
					FillEntityPropertyValues(reader, entity);
					list.Add(entity);
				}
			}
			finally
			{
				if (reader != null)
					reader.Close();
			}
		}

		public void FillEntityList(Type type, ArrayList list, Database database, DbCommand command)
		{
			var map = DbMap.GetMap(type);
			IDataReader reader = null;
			try
			{
				reader = database.ExecuteReader(command);
				while (reader.Read())
				{
					var entity = Activator.CreateInstance(type);
					FillEntityPropertyValues(reader, entity);
					list.Add(entity);
				}
			}
			finally
			{
				if (reader != null)
					reader.Close();
			}
		}

		private void MapSprocParameterValues(DbCommand command, object entityarg)
		{
			if (entityarg == null) { return; }
			var map = DbMap.GetMap(entityarg.GetType());
			foreach (IDataParameter param in command.Parameters)
			{
				var paramName = param.ParameterName.Replace("@", string.Empty);
				if (paramName == "RETURN_VALUE") { continue; }

				var entity = map.ColumnMaps.SingleOrDefault(c => c.ColumnName == paramName);
				if (entity != null)
				{
					var propertyName = entity.PropertyName;
					var paramValue = entityarg.GetType().GetProperty(propertyName).GetValue(entityarg, null);
					if (paramValue is DateTime)
					{
						var dtmValue = ((DateTime)paramValue);
						paramValue = (dtmValue == DateTime.MinValue) ? SqlDateTime.MinValue.Value : paramValue;
					}

					param.Value = (paramValue == null) ? DBNull.Value : paramValue;
				}
			}
		}

		private string ResolveSprocName(Type entityType, Type entityargType, CrudType sproctype)
		{
			var ret = string.Empty;
			var map = DbMap.GetMap(entityType);
			var argmap = DbMap.GetMap(entityargType);
			var result = map.SprocMaps.SingleOrDefault(e =>
				e.CrudType == sproctype
				&& e.EntityArgType.FullName == argmap.EntityType.FullName);

			if (result != null)
				ret = result.SprocName;

			return ret;
		}

		private string ResolveSqlString(Type entityType, Type entityargType, CrudType crudtype)
		{
			var sb = new StringBuilder();
			var entitymap = DbMap.GetMap(entityType);
			var argmap = DbMap.GetMap(entityargType);
			var tablename = entitymap.TableName;
			if (string.IsNullOrEmpty(tablename))
				throw new ArgumentNullException("Table name not found in mapping.");
			
			switch (crudtype)
			{
				case CrudType.Insert:
					var primaryKeyColumnName = string.Empty;
					sb.AppendFormat("INSERT INTO {0} (", tablename);
					foreach(var col in entitymap.ColumnMaps)
					{
						if (col.IsPrimaryKey && col.IsPrimaryKeySeededByDb)
						{ 
							primaryKeyColumnName = col.ColumnName;
							continue;
						}
						else
							sb.AppendFormat("[{0}],", col.ColumnName);
					}

					sb.Remove(sb.Length - 1, 1);
					sb.Append(")VALUES(");
					
					foreach(var col in entitymap.ColumnMaps)
					{
						if (col.IsPrimaryKey && col.IsPrimaryKeySeededByDb)
						{ continue; }
						else
							sb.AppendFormat("@{0},", col.ColumnName);
					}

					sb.Remove(sb.Length - 1, 1);
					sb.Append(")");
					sb.AppendFormat("SET @{0} = SCOPE_IDENTITY()", primaryKeyColumnName);
					break;
				case CrudType.Update:
					List<string> primaryKeyColumns = new List<string>();
					sb.AppendFormat("UPDATE {0} SET (", tablename);
		
					foreach(var col in entitymap.ColumnMaps)
					{
						if (col.IsPrimaryKey) 
						{ 
							primaryKeyColumns.Add(col.ColumnName);

							if(!col.IsPrimaryKeySeededByDb)
								sb.AppendFormat("[{0}] = @{0},", col.ColumnName);

							continue;
						}
						else
							sb.AppendFormat("[{0}] = @{0},", col.ColumnName);
					}
					sb.Remove(sb.Length - 1, 1);
					sb.Append(")");

					sb.AppendFormat("WHERE ");
					foreach (var pkcol in primaryKeyColumns)
					{
						sb.AppendFormat("[{0}] = @{0} AND  ", pkcol);
					}
					sb.Remove(sb.Length - 5, 5);
					break;
				case CrudType.Delete:
					sb.AppendFormat("DELETE {0} ", tablename);
					sb.AppendFormat("WHERE ");
					var pks = entitymap.ColumnMaps.Where(c => c.IsPrimaryKey);
					pks.ToList().ForEach(pk =>
					{
						sb.AppendFormat("[{0}] = @{0} AND ", pk.ColumnName);
					});
					sb.Remove(sb.Length - 5, 5);
					break;
			}
			return sb.ToString();
		}

		private string ResolveSprocName<TEntity, TEntityArg>(CrudType sproctype)
		{
			return ResolveSprocName(typeof(TEntity), typeof(TEntityArg), sproctype);
		}

		#region IDisposable Members

		public void Dispose()
		{
			DbMap = null;
			ParmCache = null;
		}

		#endregion
	}

	public abstract class DatabaseMapper
	{
		public DatabaseMapper()
		{
			DatabaseMaps = new Dictionary<string, object>();
		}

		private Dictionary<string, object> DatabaseMaps { get; set; }

		//public DatabaseMap<TEntity> GetMap<TEntity>()
		//{
		//	var ret = (DatabaseMap<TEntity>)DatabaseMaps.Single(tm => tm.Key == typeof(TEntity).ToString()).Value;
		//	return ret;
		//}

		public DatabaseMap GetMap(Type type)
		{
			//var ret = (DatabaseMap)DatabaseMaps.SingleOrDefault(tm => tm.Key == type.ToString()).Value;
			//if (ret == null) { ret = GetMap(type.BaseType); }

			var ret = default(DatabaseMap);
			var currtype = type;
			while (ret == null)
			{
				var result = DatabaseMaps.SingleOrDefault(e => e.Key == currtype.ToString());
				ret = (DatabaseMap)result.Value;
				currtype = type.BaseType;
				if (currtype == null) { break; }
			}

			return ret;
		}

		public DatabaseMap<TEntity> AddDatabaseMap<TEntity>(string databaseName)
		{
			var ret = new DatabaseMap<TEntity>
			{
				EntityType = typeof(TEntity),
				DatabaseName = databaseName
			};

			DatabaseMaps.Add(typeof(TEntity).ToString(), ret);
			return ret;
		}
	}

	public class DatabaseMap<TEntity> : DatabaseMap
	{
		public DatabaseMap()
		{
			EntityType = typeof(TEntity);
		}

		public DatabaseMap<TEntity> SetTableMap(string tableName)
		{
			base.SetTableMap<TEntity>(tableName);
			return this;
		}

		public DatabaseMap<TEntity> SetViewMap(string viewName)
		{
			base.SetViewMap<TEntity>(viewName);
			return this;
		}

		public DatabaseMap<TEntity> SetSprocMap(CrudType crudType, string sprocName)
		{
			base.SetSprocMap(crudType, sprocName, typeof(TEntity));
			return this;
		}

		public new DatabaseMap<TEntity> SetSprocMap(CrudType crudType, string sprocName, Type entityArgType)
		{
			base.SetSprocMap(crudType, sprocName, entityArgType);
			return this;
		}

		public DatabaseMap<TEntity> AddTableColumnMap
			(Expression<Func<TEntity, object>> propertySelector, string columnName, bool isPrimaryKey)
		{
			AddColumnMap(propertySelector, columnName, isPrimaryKey, isPrimaryKey, true, false, false);
			return this;
		}

		public DatabaseMap<TEntity> AddTableColumnMap
	(Expression<Func<TEntity, object>> propertySelector, string columnName, bool isPrimaryKey, bool isPrimaryKeySeededByDb)
		{
			AddColumnMap(propertySelector, columnName, isPrimaryKey, isPrimaryKeySeededByDb, true, false, false);
			return this;
		}

		public DatabaseMap<TEntity> AddTableColumnMap
			(Expression<Func<TEntity, object>> propertySelector, string columnName)
		{
			AddColumnMap(propertySelector, columnName, false, false, true, false, false);
			return this;
		}

		public DatabaseMap<TEntity> AddViewColumnMap
			(Expression<Func<TEntity, object>> propertySelector, string columnName)
		{
			AddColumnMap(propertySelector, columnName, false, false, false, true, false);
			return this;
		}

		public DatabaseMap<TEntity> AddSyntheticColumnMap
			(Expression<Func<TEntity, object>> propertySelector, string columnName)
		{
			AddColumnMap(propertySelector, columnName, false, false, false, false, true);
			return this;
		}

		public DatabaseMap<TEntity> AddRelationMap<TEntityChild>
			(CardinalityType cardinality, Expression<Func<TEntity, object>> propertySelector, string sprocName)
		{
			base.AddRelationMap<TEntity, TEntityChild>(cardinality, propertySelector, sprocName);
			return this;
		}

		public DatabaseMap<TEntity> AddParameterMap
			(Expression<Func<TEntity, object>> propertySelector, string parameterName)
		{
			base.AddParameterMap<TEntity>(propertySelector, parameterName);
			return this;
		}

		public DatabaseMap<TEntity> AddColumnMap
			(Expression<Func<TEntity, object>> propertySelector, string columnName, bool isPrimaryKey, bool isPrimaryKeySeededByDb, bool isTableCol, bool isViewCol, bool isSynth)
		{
			base.AddColumnMap<TEntity>(propertySelector, columnName, isPrimaryKey, isPrimaryKeySeededByDb, isTableCol, isViewCol, isSynth);
			return this;
		}
	}

	public class DatabaseMap
	{
		public DatabaseMap()
		{
			SprocMaps = new List<SprocMap>();
			ColumnMaps = new List<ColumnMap>();
			ParameterMaps = new List<ParameterMap>();
			RelationMaps = new List<RelationMap>();
		}

		public Type EntityType { get; set; }
		public string DatabaseName { get; set; }
		public string TableName { get; set; }
		//public string SelectSprocName { get; set; }
		//public string SelectManySprocName { get; set; }
		//public string InsertSprocName { get; set; }
		//public string UpdateSprocName { get; set; }
		//public string DeleteSprocName { get; set; }
		public List<SprocMap> SprocMaps { get; set; }
		public List<ColumnMap> ColumnMaps { get; set; }
		public List<ParameterMap> ParameterMaps { get; set; }
		public List<RelationMap> RelationMaps { get; set; }

		public DatabaseMap SetTableMap<TEntity>(string tableName)
		{
			TableName = tableName;
			EntityType = typeof(TEntity);
			return this;
		}

		public DatabaseMap SetViewMap<TEntity>(string viewName)
		{
			TableName = viewName;
			EntityType = typeof(TEntity);
			return this;
		}

		public DatabaseMap SetSprocMap(CrudType crudType, string sprocName, Type entityArgType)
		{
			SprocMaps.Add(new SprocMap
			{
				EntityArgType = entityArgType,
				CrudType = crudType,
				SprocName = sprocName
			});
			return this;
		}

		public DatabaseMap AddTableColumnMap<TEntity>
			(Expression<Func<TEntity, object>> propertySelector, string columnName, bool isPrimaryKey)
		{
			AddColumnMap(propertySelector, columnName, isPrimaryKey, isPrimaryKey, true, false, false);
			return this;
		}

		public DatabaseMap AddTableColumnMap<TEntity>
			(Expression<Func<TEntity, object>> propertySelector, string columnName, bool isPrimaryKey, bool isPrimaryKeySeededByDb)
		{
			AddColumnMap(propertySelector, columnName, isPrimaryKey, isPrimaryKeySeededByDb, true, false, false);
			return this;
		}


		public DatabaseMap AddTableColumnMap<TEntity>
			(Expression<Func<TEntity, object>> propertySelector, string columnName)
		{
			AddColumnMap(propertySelector, columnName, false, false, true, false, false);
			return this;
		}

		public DatabaseMap AddViewColumnMap<TEntity>
			(Expression<Func<TEntity, object>> propertySelector, string columnName)
		{
			AddColumnMap(propertySelector, columnName, false, false, false, true, false);
			return this;
		}

		public DatabaseMap AddSyntheticColumnMap<TEntity>
			(Expression<Func<TEntity, object>> propertySelector, string columnName)
		{
			AddColumnMap(propertySelector, columnName, false, false, false, false, true);
			return this;
		}

		public DatabaseMap AddRelationMap<TEntityParent, TEntityChild>
			(CardinalityType cardinality, Expression<Func<TEntityParent, object>> propertySelector, string sprocName)
		{
			var relnmap = new RelationMap
			{
				ChildType = typeof(TEntityChild),
				CardinalityType = cardinality,
				PropertyName = FormatPropertyName(propertySelector),
				SprocName = sprocName
			};

			RelationMaps.Add(relnmap);
			return this;
		}

		public DatabaseMap AddParameterMap<TEntity>
			(Expression<Func<TEntity, object>> propertySelector, string parameterName)
		{
			var propName = FormatPropertyName(propertySelector);
			var paramap = new ParameterMap
			{
				ParameterName = parameterName,
				PropertyName = propName
			};

			return this;
		}

		public DatabaseMap AddColumnMap<TEntity>
			(Expression<Func<TEntity, object>> propertySelector, string columnName, bool isPrimaryKey, bool isPrimaryKeySeededByDb, bool isTableCol, bool isViewCol, bool isSynth)
		{
			var propName = FormatPropertyName(propertySelector);
			var colmap = new ColumnMap
			{
				ColumnName = columnName,
				PropertyName = propName,
				IsPrimaryKey = isPrimaryKey,
				IsPrimaryKeySeededByDb = isPrimaryKeySeededByDb,
				IsTableColumn = isTableCol,
				IsViewColumn = isViewCol,
				IsSyntheticColumn = isSynth
			};

			ColumnMaps.Add(colmap);
			return this;
		}

		protected string FormatPropertyName<TEntity>(Expression<Func<TEntity, object>> propertySelector)
		{
			var lambdaString = propertySelector.ToString();
			var ret = lambdaString.Substring(lambdaString.IndexOf('.') + 1).Replace(")", string.Empty);
			return ret;
		}
	}

	public enum CrudType
	{
		Select, SelectMany, Insert, Update, Delete
	}

	public enum CardinalityType
	{
		OneToMany, ManyToOne, ManyToMany
	}

	public class ColumnMap
	{
		public ColumnMap() { }
		public string ColumnName { get; set; }
		public string PropertyName { get; set; }
		public bool IsPrimaryKey { get; set; }
		public bool IsPrimaryKeySeededByDb { get; set; }
		public bool IsTableColumn { get; set; }
		public bool IsViewColumn { get; set; }
		public bool IsSyntheticColumn { get; set; }
	}

	public class ParameterMap
	{
		public ParameterMap() { }
		public string ParameterName { get; set; }
		public string PropertyName { get; set; }
	}

	public class RelationMap
	{
		public RelationMap() { }
		public Type ChildType { get; set; }
		public CardinalityType CardinalityType { get; set; }
		public string PropertyName { get; set; }
		public string SprocName { get; set; }
	}

	public class SprocMap
	{
		public SprocMap() { }
		public Type EntityArgType { get; set; }
		public CrudType CrudType { get; set; }
		public string SprocName { get; set; }
	}

	internal class DataSessionDependencyMappings : DependencyMapper
	{
		public DataSessionDependencyMappings()
		{
			RegisterType<ParameterCache>()
					.CacheToScope(CacheScopeType.Singleton);
		}
	}
}
